package main

import (
	"fmt"
	"log"
	"log/syslog"
	"bytes"
	"strconv"
	"strings"
	"time"
	"os/exec"
	"path/filepath"
	"math"
)

var logger *syslog.Writer

func _syslog(format string, v ...interface {}) {
	if logger == nil {
		l, err := syslog.New(syslog.LOG_WARNING | syslog.LOG_SYSLOG, "RESOURCE_MONITOR")
		if err != nil {
			log.Printf("init syslog handler: %v", err)
			return
		}
		logger = l
	}

	logger.Warning(fmt.Sprintf(format, v...))
}

func runCmd(exe string, args ...string) (string, string, error) {
	var stdout, stderr bytes.Buffer

	cmd := exec.Command(exe, args...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()

	if err != nil {
		log.Printf("[%s] : %v\n", cmd, err)
	}

	return stdout.String(), stderr.String(), err
}

func listUnits() ([]string, error) {
	stdout, _, err := runCmd("systemctl", "--plain", "list-units")
	if err != nil {
		return nil, err
	}

	lines := strings.Split(stdout, "\n")
	lines = lines[1: len(lines) - 7]

	/*
	for _, name := range lines {
		fmt.Println("name = ", name)
	}*/

	r := make([]string, 0 , len(lines))

	for _, s := range lines {
		s = strings.TrimSpace(s)
		r = append(r, strings.Split(s, " ")[0])
	}

	
	//fmt.Printf("r = %v\n", r)
	return r, nil
}

func getCPUQuotaOfUnit(unit string) (float64, error) {
	unit = strings.TrimSpace(unit) 
	if  len(unit) == 0 {
		return math.MaxFloat64, nil
	}

	if strings.HasPrefix(unit, "-") {
		unit = strconv.Quote(unit)
	}

	stdout, _, err := runCmd("systemctl", "-p", "CPUQuotaPerSecUSec", "show", unit, "--value")
	if err != nil {
		fmt.Printf("unit [%s] error: %v\n", unit, err)
		return math.MaxFloat64, err
	}

	s := strings.TrimSpace(stdout)
	if len(s) == 0 {
		return math.MaxFloat64, nil
	} else if strings.HasPrefix(s, "infinity") {
		return math.MaxFloat64, nil
	}

	d, err := time.ParseDuration(s)
	if err != nil {
		return math.MaxFloat64, err
	}

	log.Printf("unit[%s]: %v", unit, d.Seconds())
	return d.Seconds(), nil
}

func monitor_once(quotas map[string]float64) error {
	stdout, _, err := runCmd("bash", "-c", "systemd-cgtop -n 2 -b -r --depth=5 | awk '{if ($3 != \"-\") {print $1, $3}}'")
	if err != nil {
		return err
	}

	for _, s := range strings.Split(stdout, "\n") {
		s := strings.TrimSpace(s)
		if len(s) == 0 {
			continue
		}

		infos := strings.Split(s, " ")
		unit := filepath.Base(infos[0])

		cpuUsage, _ := strconv.ParseFloat(infos[1], 0)
		cpuUsage /= 100

		fmt.Println("unit = ", unit, " cpuUsage = ", cpuUsage)

		if quota, ok := quotas[unit]; ok {
			fmt.Println("cpuUsage = ", cpuUsage, " quota = ", quota, "~~", cpuUsage/quota)
			if cpuUsage/quota >= 0.90 {
				_syslog("unit = %v cpu_usage = %0.1f quota = %v", unit, cpuUsage, quota)
			}
		}
	}

	return nil
}

func main() {
	units, err := listUnits()
	if err != nil {
		log.Fatal("list units: ", err)
	}

	/*
	for _, name := range units {
		fmt.Println("name = ", name)
	}
	fmt.Println("len = ", len(units))
	*/

	quotas := make(map[string]float64, len(units))
	for _, u := range units {
		//fmt.Println("---> u ", u)		
		q, err := getCPUQuotaOfUnit(u)
		if err != nil {
			fmt.Printf("Get CPU quota of [%v]: %v\n", u, err)
			continue
		}
		
		if q < math.MaxFloat64 {
			fmt.Printf("unit: [%v], q: [%v]", u, q)
			quotas[u] = q
		}
	}

	//fmt.Printf("---> %v\n", quotas)

	for {
		if err := monitor_once(quotas); err != nil {
			log.Fatal("run monitor: ", err)
		}
		time.Sleep(time.Second)
	}
}
